using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;
using Microsoft.Xna.Framework.Content;
using TiledLib;

namespace ContentPipelineExtension
{
    public class MapCollectionXmlContent
    {
        public List<MapXmlContent> Maps = new List<MapXmlContent>();
    }

    [ContentSerializerRuntimeType("JustinsGame.Engine.MapCollection, JustinsGame")]
    public class MapCollectionContent
    {
        public List<TempMapContent> Maps = new List<TempMapContent>();
    }

    /// <summary>
    /// This class will be instantiated by the XNA Framework Content Pipeline
    /// to apply custom processing to content data, converting an object of
    /// type TInput to TOutput. The input and output types may be the same if
    /// the processor wishes to alter data without changing its type.
    ///
    /// This should be part of a Content Pipeline Extension Library project.
    ///
    /// TODO: change the ContentProcessor attribute to specify the correct
    /// display name for this processor.
    /// </summary>
    [ContentProcessor(DisplayName = "ContentPipelineExtension.MapCollectionProcessor")]
    public class MapCollectionProcessor : ContentProcessor<MapCollectionXmlContent, MapCollectionContent>
    {
        /// <summary>Structure of a map collection is as follows:
        /// .tmx file
        /// grid position (x, y)
        /// bullet spawner (bulletML pattern)
        /// list of events
        ///    list of waves
        ///       list of enemies
        ///    list of triggers
        /// </summary>
        /// <param name="input"></param>
        /// <param name="context"></param>
        /// <returns></returns>
        public override MapCollectionContent Process(MapCollectionXmlContent input, ContentProcessorContext context)
        {
            MapCollectionContent mapCollection = new MapCollectionContent();

            foreach (MapXmlContent mapXml in input.Maps)
            {
                // load TMX map file at specified path
                ExternalReference<MapContent> externalMapFile = new ExternalReference<MapContent>(mapXml.Path);
                TempMapContent tempMapContent = context.BuildAndLoadAsset<MapContent, TempMapContent>(externalMapFile, "TiledMapProcessor");

                tempMapContent.GridPosition = mapXml.GridPosition;
                tempMapContent.BulletSpawner = mapXml.BulletSpawner;

                // get and build up the events that belong to this map
                GetEventsAndAddToOutput(mapXml, tempMapContent);

                // we do not allow two maps to exist at the same grid position
                if (IsMapAtPositionAlready(mapCollection, tempMapContent.GridPosition.X, tempMapContent.GridPosition.Y))
                    throw new Exception(String.Format("Duplicate grid position at [{0},{1}] found for map area '{2}'.", tempMapContent.GridPosition.X, tempMapContent.GridPosition.Y, mapXml.Path));
                else
                    mapCollection.Maps.Add(tempMapContent);
            }

            return mapCollection;
        }

        /// <summary>Check if a map in the collection already exists as the passed x,y coordinates.
        /// </summary>
        /// <param name="mapCollection"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns></returns>
        private bool IsMapAtPositionAlready(MapCollectionContent mapCollection, float x, float y)
        {
            TempMapContent possiblyExisting = mapCollection.Maps.Find(
                m => (int)m.GridPosition.X == (int)x && (int)m.GridPosition.Y == (int)y
            );

            if (possiblyExisting != null)
                return true;
            else
                return false;
        }

        /// <summary>Get the triggers, associated actions, and subscriber ids for the passed map event.
        /// </summary>
        /// <param name="mapEventXml"></param>
        /// <param name="tempMapEvent"></param>
        private void GetTriggersAndAddToOutput(TempMapContent tempMapContent, MapEventXmlContent mapEventXml, TempMapEventContent tempMapEvent)
        {
            foreach (TriggerXmlContent triggerXml in mapEventXml.Triggers)
            {
                TriggerContent trigger = new TriggerContent();
                trigger.Type = triggerXml.Type;

                foreach (TriggerActionXmlContent actionXml in triggerXml.Actions)
                {
                    TriggerActionContent action = new TriggerActionContent();
                    action.Type = actionXml.Type;
                    action.EntityId = actionXml.EntityId;

                    PathContent path = tempMapContent.Paths.Find(p => p.Id == actionXml.Path);
                    if (path != null)
                        foreach (Vector2 destination in path.Destinations)
                            action.Path.Add(destination);

                    foreach (int subscriberId in actionXml.SubscriberIds)
                        action.SubscriberIds.Add(subscriberId);

                    trigger.Actions.Add(action);
                }

                tempMapEvent.Triggers.Add(trigger);
            }
        }

        /// <summary>Get the enemies and paths for the passed wave
        /// </summary>
        /// <param name="tempMapContent"></param>
        /// <param name="mapEventWaveXml"></param>
        /// <param name="tempMapEventWave"></param>
        private void GetEnemiesAndAddToOutput(TempMapContent tempMapContent, MapEventWaveXmlContent mapEventWaveXml, TempMapEventWaveContent tempMapEventWave)
        {
            foreach (EnemyEntityXmlContent enemyEntityXml in mapEventWaveXml.Enemies)
            {
                TempMapEventWaveEnemyContent tempMapEventWaveEnemy = new TempMapEventWaveEnemyContent();
                tempMapEventWaveEnemy.Count = enemyEntityXml.Count;
                tempMapEventWaveEnemy.HasWeapon = enemyEntityXml.HasWeapon;
                tempMapEventWaveEnemy.Type = enemyEntityXml.Type;

                PathContent path = tempMapContent.Paths.Find(p => p.Id == enemyEntityXml.Path);
                if (path != null)
                    foreach (Vector2 destination in path.Destinations)
                        tempMapEventWaveEnemy.Path.Add(destination);

                tempMapEventWave.Enemies.Add(tempMapEventWaveEnemy);
            }
        }

        /// <summary>Get the waves and enemies for the passed map event
        /// </summary>
        /// <param name="tempMapContent"></param>
        /// <param name="mapEventXml"></param>
        /// <param name="tempMapEvent"></param>
        private void GetWavesAndAddToOutput(TempMapContent tempMapContent, MapEventXmlContent mapEventXml, TempMapEventContent tempMapEvent)
        {
            // waves in events
            foreach (MapEventWaveXmlContent mapEventWaveXml in mapEventXml.Waves)
            {
                TempMapEventWaveContent tempMapEventWave = new TempMapEventWaveContent();
                tempMapEventWave.MillisecondsBetweenEnemies = mapEventWaveXml.MillisecondsBetweenEnemies;

                // enemies in waves
                GetEnemiesAndAddToOutput(tempMapContent, mapEventWaveXml, tempMapEventWave);

                tempMapEvent.Waves.Add(tempMapEventWave);
            }
        }

        /// <summary>Get the events, waves, and triggers for the passed map
        /// </summary>
        /// <param name="mapXml"></param>
        /// <param name="tempMapContent"></param>
        private void GetEventsAndAddToOutput(MapXmlContent mapXml, TempMapContent tempMapContent)
        {
            foreach (MapEventXmlContent mapEventXml in mapXml.Events)
            {
                TempMapEventContent tempMapEvent = new TempMapEventContent();
                tempMapEvent.SecondsBetweenWaves = mapEventXml.SecondsBetweenWaves;

                GetWavesAndAddToOutput(tempMapContent, mapEventXml, tempMapEvent);

                GetTriggersAndAddToOutput(tempMapContent, mapEventXml, tempMapEvent);

                tempMapContent.Events.Add(tempMapEvent);
            }
        }
    }
}